﻿//++
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// Module Name:
//
//    UserMetrics.cs
//
// Abstract:
//    
//    Collects channel and rule statistics for each user.
//
//    Note: THIS CODE IS NOT TO BE SHIPPED.
//
//--

using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.IO;
using Microsoft.EnhancedReminders;

namespace Microsoft.EnhancedRemindersTools
{
    internal class UserMetrics
    {
        private User _currentUser; // Collating channel and rules data for this user.

        private FrequencyCounter _ocsChannelsPerUser = new FrequencyCounter(5, false);
        private int _ocsCommunicatorCount;
        private int _ocsWorkPhoneCount;
        private int _ocsMobilePhoneCount;
        private int _ocsHomePhoneCount;
        private int _ocsOtherPhoneCount;

        private FrequencyCounter _customChannelsPerUser = new FrequencyCounter(5, true);
        private int _customIMCount;
        private int _customPhoneCount;
        private int _customSMSCount;

        private int _rulesPerOCSChannel;
        private int _rulesPerCustomChannel;

        private FrequencyCounter _rulesPerUser = new FrequencyCounter(20, true);

        private const int NewRuleWindowInDays = 5; // For looking for recently-created rules.
        private int _newlyCreatedRules;

        private int _voicemailRuleCount;
        private int _voicemailRuleTimeCount;
        private int _voicemailRuleSenderCount;
        private int _voicemailRulePresenceCount;
        private int _voicemailRuleMultipleCount;

        private int _calendarRuleCount;
        private int _autojoinRuleCount;
        private int _calendarAndAutojoinRuleCount;

        /// <summary>
        /// Update user metrics if the message string is relevant.
        /// </summary>
        /// <param name="userId">The user ID.</param>
        /// <param name="message">The log message string to analyze.</param>
        internal void Update(Guid userId, string message)
        {
            if (String.IsNullOrEmpty(message))
            {
                return;
            }

            try
            {
                if (message.StartsWith("ExchangeUserProxy.Monitor_StorageItemRetrieved"))
                {
                    StartNewUser(userId); // Matches the start of a channel/rule log sequence.
                }
                else if (message.StartsWith("Channel id="))
                {
                    UpdateChannelForUser(userId, message); // Matches a channel description.
                }
                else if (message.StartsWith("Rule name="))
                {
                    UpdateRuleForUser(userId, message); // Matches a rule description.
                }
                else if (message.Equals("Applying new rules."))
                {
                    CompleteUserInfo(userId); // Completes the end of a channel/rule block.
                }
            }
            catch (FormatException exn)
            {
                Console.WriteLine("The following error occurred while processing the user data entry '{0}': {1}", message, exn.ToString());
            }
            catch (ArgumentException exn)
            {
                Console.WriteLine("The following error occurred while processing the user data entry '{0}': {1}", message, exn.ToString());
            }
        }

        /// <summary>
        /// Start collecting data for a new user.
        /// </summary>
        /// <param name="userId">The user ID.</param>
        private void StartNewUser(Guid userId)
        {
            Debug.Assert(_currentUser == null || _currentUser.Id != userId, "An existing user has the same user ID");

            _currentUser = new User(userId);
        }

        /// <summary>
        /// Add a new channel description for the current user.
        /// </summary>
        /// <param name="userId">The user ID.</param>
        /// <param name="channelInfo">The channel descriptor.</param>
        private void UpdateChannelForUser(Guid userId, string channelInfo)
        {
            if (_currentUser == null)
            {
                return; // Missed the start for this user so ignore.
            }

            Debug.Assert(userId == _currentUser.Id, "Unexpected user ID");
            Debug.Assert(!String.IsNullOrEmpty(channelInfo), "channelInfo");

            _currentUser.UpdateChannel(channelInfo);
        }

        /// <summary>
        /// Add a new rule description for the current user.
        /// </summary>
        /// <param name="userId">The user ID.</param>
        /// <param name="ruleInfo">The rule descriptor.</param>
        private void UpdateRuleForUser(Guid userId, string ruleInfo)
        {
            if (_currentUser == null)
            {
                return; // Missed the start for this user so ignore.
            }

            Debug.Assert(userId == _currentUser.Id, "Unexpected user ID");
            Debug.Assert(!String.IsNullOrEmpty(ruleInfo), "ruleInfo");

            RuleDescription rule = new RuleDescription(ruleInfo); // Parse the rule descriptor string.

            // Now update per-rule metrics...

            foreach (Guid id in rule.NotificationChannels)
            {
                if (IsOcsChannelId(id))
                {
                    ++_rulesPerOCSChannel;
                }
                else
                {
                    ++_rulesPerCustomChannel;
                }
            }

            if ((DateTime.Now.Ticks - rule.Timestamp.Ticks) / TimeSpan.TicksPerDay < NewRuleWindowInDays)
            {
                ++_newlyCreatedRules; // Update count of recently (in the last N days) created rules.
            }

            if (rule.IsEnabled)
            {
                if (rule.Type == MailboxType.Voicemail)
                {
                    ++_voicemailRuleCount;

                    if (rule.Conditions.Length > 1)
                    {
                        ++_voicemailRuleMultipleCount;
                    }
                    foreach (string condition in rule.Conditions)
                    {
                        if (condition == "Time")
                        {
                            ++_voicemailRuleTimeCount;
                        }
                        else if (condition == "Sender")
                        {
                            ++_voicemailRuleSenderCount;
                        }
                        else if (condition == "Presence")
                        {
                            ++_voicemailRulePresenceCount;
                        }
                        else
                        {
                            Debug.Fail("Unexpected condition type");
                        }
                    }
                }
                else if (rule.Type == MailboxType.CalendarItem)
                {
                    Debug.Assert(rule.NotificationChannels != null, "rule.NotificationChannels");
                    Debug.Assert(rule.AutoJoinChannels != null, "rule.AutoJoinChannels");

                    if (rule.AutoJoinChannels.Count > 0)
                    {
                        ++_autojoinRuleCount;
                        if (rule.NotificationChannels.Count > 0)
                        {
                            ++_calendarAndAutojoinRuleCount;
                        }
                    }
                    else if (rule.NotificationChannels.Count > 0)
                    {
                        ++_calendarRuleCount;
                    }
                }
                else
                {
                    Debug.Fail("Unexpected rule type");
                }
            }

            // Add the rule to the user for later updating of user rule statistics.
            _currentUser.UpdateRule(ruleInfo);
        }

        /// <summary>
        /// Helper method to determine a channel ID maps to an OCS channel.
        /// </summary>
        /// <param name="id">The channel ID.</param>
        private bool IsOcsChannelId(Guid id)
        {
            if (id == NotificationChannel.CommunicatorId ||
                id == NotificationChannel.WorkPhoneId ||
                id == NotificationChannel.MobilePhoneId ||
                id == NotificationChannel.HomePhoneId ||
                id == NotificationChannel.OtherPhoneId)
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// Called when the end of a block of channel/rule log entries has been reached for a user.
        /// </summary>
        /// <param name="userId">The user ID.</param>
        private void CompleteUserInfo(Guid userId)
        {
            if (_currentUser == null)
            {
                return; // Missed the start for this user or the message "Applying new rules" is being logged in a different context.
            }

            Debug.Assert(userId == _currentUser.Id, "Unexpected user ID");

            UpdateUserChannelMetrics(_currentUser.Channels);
            UpdateUserRuleMetrics(_currentUser.Rules);

            // We're done with this user.  Set _currentUser to null to prevent updating metrics if random instances 
            // of the log string "Applying new rules" are found.
            _currentUser = null;
        }

        /// <summary>
        /// Update per-user channel metrics.
        /// </summary>
        /// <param name="channels">The list of channels for the current user.</param>
        private void UpdateUserChannelMetrics(List<NotificationChannel> channels)
        {
            Debug.Assert(channels != null, "channels");

            // Update metrics that require all user entries
            uint ocsChannelsCount = 0;
            uint customChannelCount = 0;
            foreach (NotificationChannel channel in channels)
            {
                if (channel.GetType() == typeof(NotificationChannel) && channel.IsEnabled)
                {
                    ++ocsChannelsCount;

                    if (channel.Id == NotificationChannel.CommunicatorId)
                    {
                        ++_ocsCommunicatorCount;
                    }
                    else if (channel.Id == NotificationChannel.WorkPhoneId)
                    {
                        ++_ocsWorkPhoneCount;
                    }
                    else if (channel.Id == NotificationChannel.MobilePhoneId)
                    {
                        ++_ocsMobilePhoneCount;
                    }
                    else if (channel.Id == NotificationChannel.HomePhoneId)
                    {
                        ++_ocsHomePhoneCount;
                    }
                    else if (channel.Id == NotificationChannel.OtherPhoneId)
                    {
                        ++_ocsOtherPhoneCount;
                    }
                    else
                    {
                        Debug.Fail("Unexpected OCS channel type.");
                    }
                }
                else if (channel.GetType() == typeof(NotificationCustomChannel) && channel.IsEnabled)
                {
                    ++customChannelCount;

                    if (channel.NotificationChannelType == NotificationChannelType.Communicator)
                    {
                        ++_customIMCount;
                    }
                    else if (channel.NotificationChannelType == NotificationChannelType.Phone)
                    {
                        ++_customPhoneCount;
                    }
                    else if (channel.NotificationChannelType == NotificationChannelType.SMS)
                    {
                        ++_customSMSCount;
                    }
                    else
                    {
                        Debug.Fail("Unexpected custom channel type " + channel.NotificationChannelType);
                    }
                }
            }

            // Update the OCS and custom channel frequency counters.
            _ocsChannelsPerUser.Update(ocsChannelsCount);
            _customChannelsPerUser.Update(customChannelCount);
        }

        /// <summary>
        /// Update the per-user rule metrics.
        /// </summary>
        /// <param name="rules">The rules for the current user.</param>
        private void UpdateUserRuleMetrics(List<RuleDescription> rules)
        {
            uint enabledRuleCount = 0;
            foreach (RuleDescription rule in rules)
            {
                if (rule.IsEnabled)
                {
                    ++enabledRuleCount;
                }
            }

            _rulesPerUser.Update(enabledRuleCount);
        }

        /// <summary>
        /// Write the user data metrics out to the summary file.
        /// </summary>
        /// <param name="summaryFile">The summary file.</param>
        internal void WriteData(StreamWriter summaryFile)
        {
            Debug.Assert(summaryFile != null, "summaryFile");

            _ocsChannelsPerUser.WriteSummary(summaryFile, "Number of enabled OCS channels per user: ", "{0} channels - {1} users");

            summaryFile.WriteLine("Frequency of OCS channel types per user: Communicator - {0}, Work Phone - {1}, Mobile Phone - {2}, Home Phone - {3}, Other Phone - {4}",
                _ocsCommunicatorCount, _ocsWorkPhoneCount, _ocsMobilePhoneCount, _ocsHomePhoneCount, _ocsOtherPhoneCount);

            summaryFile.WriteLine("Number of rules configured to deliver ER to OCS channels: {0} across all users", _rulesPerOCSChannel);

            _customChannelsPerUser.WriteSummary(summaryFile, "Number of enabled custom channels per user: ", "{0} channels - {1} users");

            summaryFile.WriteLine("Frequency of custom channel types per user: IM - {0}, Phone - {1}, SMS - {2}",
                _customIMCount, _customPhoneCount, _customSMSCount);

            summaryFile.WriteLine("Number of rules configured to deliver ER to custom channels: {0} across all users", _rulesPerCustomChannel);

            _rulesPerUser.WriteSummary(summaryFile, "Number of enabled rules per user: ", "{0} rules - {1} users");

            summaryFile.WriteLine("Number of newly created rules (created in last {0} days): {1} newly created rules across all users", NewRuleWindowInDays, _newlyCreatedRules);

            summaryFile.WriteLine("Number of enabled voicemail rules: {0}", _voicemailRuleCount);
            summaryFile.WriteLine("Number of enabled voicemail rules by time condition: {0}", _voicemailRuleTimeCount);
            summaryFile.WriteLine("Number of enabled voicemail rules by sender condition: {0}", _voicemailRuleSenderCount);
            summaryFile.WriteLine("Number of enabled voicemail rules by presence condition: {0}", _voicemailRulePresenceCount);
            summaryFile.WriteLine("Number of enabled voicemail rules by multiple conditions: {0}", _voicemailRuleMultipleCount);

            summaryFile.WriteLine("Number of enabled calendar rules: {0}", _calendarRuleCount);
            summaryFile.WriteLine("Number of enabled AutoJoin rules: {0}", _autojoinRuleCount);
            summaryFile.WriteLine("Number of enabled calendar and AutoJoin rules: {0}", _calendarAndAutojoinRuleCount);
        }
    }
}
